/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- *//* vim: set ts=8 sts=2 et sw=2 tw=80: *//* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */#include"nsSHistory.h"#include<algorithm>#include"nsCOMArray.h"#include"nsComponentManagerUtils.h"#include"nsDocShell.h"#include"nsIContentViewer.h"#include"nsIDocShell.h"#include"nsIDocShellLoadInfo.h"#include"nsIDocShellTreeItem.h"#include"nsILayoutHistoryState.h"#include"nsIObserverService.h"#include"nsISHContainer.h"#include"nsISHEntry.h"#include"nsISHistoryListener.h"#include"nsISHTransaction.h"#include"nsIURI.h"#include"nsNetUtil.h"#include"nsTArray.h"#include"prsystem.h"#include"mozilla/Attributes.h"#include"mozilla/LinkedList.h"#include"mozilla/MathAlgorithms.h"#include"mozilla/Preferences.h"#include"mozilla/Services.h"#include"mozilla/StaticPtr.h"#include"mozilla/dom/TabGroup.h"usingnamespacemozilla;#define PREF_SHISTORY_SIZE "browser.sessionhistory.max_entries"#define PREF_SHISTORY_MAX_TOTAL_VIEWERS "browser.sessionhistory.max_total_viewers"#define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout"// Default this to time out unused content viewers after 30 minutes#define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30 * 60)staticconstchar*kObservedPrefs[]={PREF_SHISTORY_SIZE,PREF_SHISTORY_MAX_TOTAL_VIEWERS,nullptr};staticint32_tgHistoryMaxSize=50;// List of all SHistory objects, used for content viewer cache evictionstaticLinkedList<nsSHistory>gSHistoryList;// Max viewers allowed total, across all SHistory objects - negative default// means we will calculate how many viewers to cache based on total memoryint32_tnsSHistory::sHistoryMaxTotalViewers=-1;// A counter that is used to be able to know the order in which// entries were touched, so that we can evict older entries first.staticuint32_tgTouchCounter=0;staticLazyLogModulegSHistoryLog("nsSHistory");#define LOG(format) MOZ_LOG(gSHistoryLog, mozilla::LogLevel::Debug, format)// This macro makes it easier to print a log message which includes a URI's// spec. Example use://// nsIURI *uri = [...];// LOG_SPEC(("The URI is %s.", _spec), uri);//#define LOG_SPEC(format, uri) \ PR_BEGIN_MACRO \ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ nsAutoCString _specStr(NS_LITERAL_CSTRING("(null)"));\ if (uri) { \ _specStr = uri->GetSpecOrDefault(); \ } \ const char* _spec = _specStr.get(); \ LOG(format); \ } \ PR_END_MACRO// This macro makes it easy to log a message including an SHEntry's URI.// For example://// nsCOMPtr<nsISHEntry> shentry = [...];// LOG_SHENTRY_SPEC(("shentry %p has uri %s.", shentry.get(), _spec), shentry);//#define LOG_SHENTRY_SPEC(format, shentry) \ PR_BEGIN_MACRO \ if (MOZ_LOG_TEST(gSHistoryLog, LogLevel::Debug)) { \ nsCOMPtr<nsIURI> uri; \ shentry->GetURI(getter_AddRefs(uri)); \ LOG_SPEC(format, uri); \ } \ PR_END_MACRO// Iterates over all registered session history listeners.#define ITERATE_LISTENERS(body) \ PR_BEGIN_MACRO \ { \ nsAutoTObserverArray<nsWeakPtr, 2>::EndLimitedIterator \ iter(mListeners); \ while (iter.HasMore()) { \ nsCOMPtr<nsISHistoryListener> listener = \ do_QueryReferent(iter.GetNext()); \ if (listener) { \ body \ } \ } \ } \ PR_END_MACRO// Calls a given method on all registered session history listeners.#define NOTIFY_LISTENERS(method, args) \ ITERATE_LISTENERS( \ listener->method args; \ );// Calls a given method on all registered session history listeners.// Listeners may return 'false' to cancel an action so make sure that we// set the return value to 'false' if one of the listeners wants to cancel.#define NOTIFY_LISTENERS_CANCELABLE(method, retval, args) \ PR_BEGIN_MACRO \ { \ bool canceled = false; \ retval = true; \ ITERATE_LISTENERS( \ listener->method args; \ if (!retval) { \ canceled = true; \ } \ ); \ if (canceled) { \ retval = false; \ } \ } \ PR_END_MACROenumHistCmd{HIST_CMD_BACK,HIST_CMD_FORWARD,HIST_CMD_GOTOINDEX,HIST_CMD_RELOAD};classnsSHistoryObserverfinal:publicnsIObserver{public:NS_DECL_ISUPPORTSNS_DECL_NSIOBSERVERnsSHistoryObserver(){}protected:~nsSHistoryObserver(){}};StaticRefPtr<nsSHistoryObserver>gObserver;NS_IMPL_ISUPPORTS(nsSHistoryObserver,nsIObserver)NS_IMETHODIMPnsSHistoryObserver::Observe(nsISupports*aSubject,constchar*aTopic,constchar16_t*aData){if(!strcmp(aTopic,NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)){nsSHistory::UpdatePrefs();nsSHistory::GloballyEvictContentViewers();}elseif(!strcmp(aTopic,"cacheservice:empty-cache")||!strcmp(aTopic,"memory-pressure")){nsSHistory::GloballyEvictAllContentViewers();}returnNS_OK;}namespace{already_AddRefed<nsIContentViewer>GetContentViewerForTransaction(nsISHTransaction*aTrans){nsCOMPtr<nsISHEntry>entry;aTrans->GetSHEntry(getter_AddRefs(entry));if(!entry){returnnullptr;}nsCOMPtr<nsISHEntry>ownerEntry;nsCOMPtr<nsIContentViewer>viewer;entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),getter_AddRefs(viewer));returnviewer.forget();}}// namespacevoidnsSHistory::EvictContentViewerForTransaction(nsISHTransaction*aTrans){nsCOMPtr<nsISHEntry>entry;aTrans->GetSHEntry(getter_AddRefs(entry));nsCOMPtr<nsIContentViewer>viewer;nsCOMPtr<nsISHEntry>ownerEntry;entry->GetAnyContentViewer(getter_AddRefs(ownerEntry),getter_AddRefs(viewer));if(viewer){NS_ASSERTION(ownerEntry,"Content viewer exists but its SHEntry is null");LOG_SHENTRY_SPEC(("Evicting content viewer 0x%p for ""owning SHEntry 0x%p at %s.",viewer.get(),ownerEntry.get(),_spec),ownerEntry);// Drop the presentation state before destroying the viewer, so that// document teardown is able to correctly persist the state.ownerEntry->SetContentViewer(nullptr);ownerEntry->SyncPresentationState();viewer->Destroy();}// When dropping bfcache, we have to remove associated dynamic entries as well.int32_tindex=-1;GetIndexOfEntry(entry,&index);if(index!=-1){nsCOMPtr<nsISHContainer>container(do_QueryInterface(entry));RemoveDynEntries(index,container);}}nsSHistory::nsSHistory():mIndex(-1),mLength(0),mRequestedIndex(-1),mGlobalIndexOffset(0),mEntriesInFollowingPartialHistories(0),mRootDocShell(nullptr),mIsPartial(false){// Add this new SHistory object to the listgSHistoryList.insertBack(this);}nsSHistory::~nsSHistory(){}NS_IMPL_ADDREF(nsSHistory)NS_IMPL_RELEASE(nsSHistory)NS_INTERFACE_MAP_BEGIN(nsSHistory)NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports,nsISHistory)NS_INTERFACE_MAP_ENTRY(nsISHistory)NS_INTERFACE_MAP_ENTRY(nsIWebNavigation)NS_INTERFACE_MAP_ENTRY(nsISHistoryInternal)NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)NS_INTERFACE_MAP_END// staticuint32_tnsSHistory::CalcMaxTotalViewers(){// Calculate an estimate of how many ContentViewers we should cache based// on RAM. This assumes that the average ContentViewer is 4MB (conservative)// and caps the max at 8 ContentViewers//// TODO: Should we split the cache memory betw. ContentViewer caching and// nsCacheService?//// RAM ContentViewers// -----------------------// 32 Mb 0// 64 Mb 1// 128 Mb 2// 256 Mb 3// 512 Mb 5// 1024 Mb 8// 2048 Mb 8// 4096 Mb 8uint64_tbytes=PR_GetPhysicalMemorySize();if(bytes==0){return0;}// Conversion from unsigned int64_t to double doesn't work on all platforms.// We need to truncate the value at INT64_MAX to make sure we don't// overflow.if(bytes>INT64_MAX){bytes=INT64_MAX;}doublekBytesD=(double)(bytes>>10);// This is essentially the same calculation as for nsCacheService,// except that we divide the final memory calculation by 4, since// we assume each ContentViewer takes on average 4MBuint32_tviewers=0;doublex=std::log(kBytesD)/std::log(2.0)-14;if(x>0){viewers=(uint32_t)(x*x-x+2.001);// add .001 for roundingviewers/=4;}// Cap it off at 8 maxif(viewers>8){viewers=8;}returnviewers;}// staticvoidnsSHistory::UpdatePrefs(){Preferences::GetInt(PREF_SHISTORY_SIZE,&gHistoryMaxSize);Preferences::GetInt(PREF_SHISTORY_MAX_TOTAL_VIEWERS,&sHistoryMaxTotalViewers);// If the pref is negative, that means we calculate how many viewers// we think we should cache, based on total memoryif(sHistoryMaxTotalViewers<0){sHistoryMaxTotalViewers=CalcMaxTotalViewers();}}// staticnsresultnsSHistory::Startup(){UpdatePrefs();// The goal of this is to unbreak users who have inadvertently set their// session history size to less than the default value.int32_tdefaultHistoryMaxSize=Preferences::GetDefaultInt(PREF_SHISTORY_SIZE,50);if(gHistoryMaxSize<defaultHistoryMaxSize){gHistoryMaxSize=defaultHistoryMaxSize;}// Allow the user to override the max total number of cached viewers,// but keep the per SHistory cached viewer limit constantif(!gObserver){gObserver=newnsSHistoryObserver();Preferences::AddStrongObservers(gObserver,kObservedPrefs);nsCOMPtr<nsIObserverService>obsSvc=mozilla::services::GetObserverService();if(obsSvc){// Observe empty-cache notifications so tahat clearing the disk/memory// cache will also evict all content viewers.obsSvc->AddObserver(gObserver,"cacheservice:empty-cache",false);// Same for memory-pressure notificationsobsSvc->AddObserver(gObserver,"memory-pressure",false);}}returnNS_OK;}// staticvoidnsSHistory::Shutdown(){if(gObserver){Preferences::RemoveObservers(gObserver,kObservedPrefs);nsCOMPtr<nsIObserverService>obsSvc=mozilla::services::GetObserverService();if(obsSvc){obsSvc->RemoveObserver(gObserver,"cacheservice:empty-cache");obsSvc->RemoveObserver(gObserver,"memory-pressure");}gObserver=nullptr;}}/* Add an entry to the History list at mIndex and * increment the index to point to the new entry */NS_IMETHODIMPnsSHistory::AddEntry(nsISHEntry*aSHEntry,boolaPersist){NS_ENSURE_ARG(aSHEntry);nsCOMPtr<nsISHistory>shistoryOfEntry;aSHEntry->GetSHistory(getter_AddRefs(shistoryOfEntry));if(shistoryOfEntry&&shistoryOfEntry!=this){NS_WARNING("The entry has been associated to another nsISHistory instance. ""Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() ""first if you're copying an entry from another nsISHistory.");returnNS_ERROR_FAILURE;}aSHEntry->SetSHistory(this);// If we have a root docshell, update the docshell id of the root shentry to// match the id of that docshellif(mRootDocShell){nsIDdocshellID=mRootDocShell->HistoryID();aSHEntry->SetDocshellID(&docshellID);}nsCOMPtr<nsISHTransaction>currentTxn;if(mListRoot){GetTransactionAtIndex(mIndex,getter_AddRefs(currentTxn));}boolcurrentPersist=true;if(currentTxn){currentTxn->GetPersist(¤tPersist);}int32_tcurrentIndex=mIndex;if(!currentPersist){NOTIFY_LISTENERS(OnHistoryReplaceEntry,(currentIndex));NS_ENSURE_SUCCESS(currentTxn->SetSHEntry(aSHEntry),NS_ERROR_FAILURE);currentTxn->SetPersist(aPersist);returnNS_OK;}nsCOMPtr<nsISHTransaction>txn(do_CreateInstance(NS_SHTRANSACTION_CONTRACTID));NS_ENSURE_TRUE(txn,NS_ERROR_FAILURE);nsCOMPtr<nsIURI>uri;aSHEntry->GetURI(getter_AddRefs(uri));NOTIFY_LISTENERS(OnHistoryNewEntry,(uri,currentIndex));// If a listener has changed mIndex, we need to get currentTxn again,// otherwise we'll be left at an inconsistent state (see bug 320742)if(currentIndex!=mIndex){GetTransactionAtIndex(mIndex,getter_AddRefs(currentTxn));}// Set the ShEntry and parent for the transaction. setting the// parent will properly set the parent child relationshiptxn->SetPersist(aPersist);NS_ENSURE_SUCCESS(txn->Create(aSHEntry,currentTxn),NS_ERROR_FAILURE);// A little tricky math here... Basically when adding an object regardless of// what the length was before, it should always be set back to the current and// lop off the forward.mLength=(++mIndex+1);NOTIFY_LISTENERS(OnLengthChanged,(mLength));NOTIFY_LISTENERS(OnIndexChanged,(mIndex));// Much like how mLength works above, when changing our entries, all following// partial histories should be purged, so we just reset the number to zero.mEntriesInFollowingPartialHistories=0;// If this is the very first transaction, initialize the listif(!mListRoot){mListRoot=txn;}// Purge History list if it is too longif(gHistoryMaxSize>=0&&mLength>gHistoryMaxSize){PurgeHistory(mLength-gHistoryMaxSize);}returnNS_OK;}NS_IMETHODIMPnsSHistory::GetIsPartial(bool*aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=mIsPartial;returnNS_OK;}/* Get size of the history list */NS_IMETHODIMPnsSHistory::GetCount(int32_t*aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=mLength;returnNS_OK;}NS_IMETHODIMPnsSHistory::GetGlobalCount(int32_t*aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=mGlobalIndexOffset+mLength+mEntriesInFollowingPartialHistories;returnNS_OK;}NS_IMETHODIMPnsSHistory::GetGlobalIndexOffset(int32_t*aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=mGlobalIndexOffset;returnNS_OK;}NS_IMETHODIMPnsSHistory::OnPartialSHistoryActive(int32_taGlobalLength,int32_taTargetIndex){NS_ENSURE_TRUE(mRootDocShell&&mIsPartial,NS_ERROR_UNEXPECTED);int32_textraLength=aGlobalLength-mLength-mGlobalIndexOffset;NS_ENSURE_TRUE(extraLength>=0,NS_ERROR_UNEXPECTED);if(extraLength!=mEntriesInFollowingPartialHistories){mEntriesInFollowingPartialHistories=extraLength;}returnRestoreToEntryAtIndex(aTargetIndex);}NS_IMETHODIMPnsSHistory::OnPartialSHistoryDeactive(){NS_ENSURE_TRUE(mRootDocShell&&mIsPartial,NS_ERROR_UNEXPECTED);// Ensure the deactive docshell loads about:blank.nsCOMPtr<nsIWebNavigation>webNav=do_QueryInterface(mRootDocShell);nsCOMPtr<nsIURI>currentURI;webNav->GetCurrentURI(getter_AddRefs(currentURI));if(NS_IsAboutBlank(currentURI)){returnNS_OK;}// At this point we've swapped out to an invisble tab, and can not prompt here.// The check should have been done in nsDocShell::InternalLoad, so we'd// just force docshell to load about:blank.if(NS_FAILED(mRootDocShell->ForceCreateAboutBlankContentViewer(nullptr))){returnNS_ERROR_FAILURE;}returnNS_OK;}/* Get index of the history list */NS_IMETHODIMPnsSHistory::GetIndex(int32_t*aResult){NS_PRECONDITION(aResult,"null out param?");*aResult=mIndex;returnNS_OK;}NS_IMETHODIMPnsSHistory::GetGlobalIndex(int32_t*aResult){NS_PRECONDITION(aResult,"null out param?");*aResult=mIndex+mGlobalIndexOffset;returnNS_OK;}/* Get the requestedIndex */NS_IMETHODIMPnsSHistory::GetRequestedIndex(int32_t*aResult){NS_PRECONDITION(aResult,"null out param?");*aResult=mRequestedIndex;returnNS_OK;}/* Get the entry at a given index */NS_IMETHODIMPnsSHistory::GetEntryAtIndex(int32_taIndex,boolaModifyIndex,nsISHEntry**aResult){nsresultrv;nsCOMPtr<nsISHTransaction>txn;/* GetTransactionAtIndex ensures aResult is valid and validates aIndex */rv=GetTransactionAtIndex(aIndex,getter_AddRefs(txn));if(NS_SUCCEEDED(rv)&&txn){// Get the Entry from the transactionrv=txn->GetSHEntry(aResult);if(NS_SUCCEEDED(rv)&&(*aResult)){// Set mIndex to the requested index, if asked to do so..if(aModifyIndex){mIndex=aIndex;NOTIFY_LISTENERS(OnIndexChanged,(mIndex))}}}returnrv;}/* Get the transaction at a given index */NS_IMETHODIMPnsSHistory::GetTransactionAtIndex(int32_taIndex,nsISHTransaction**aResult){nsresultrv;NS_ENSURE_ARG_POINTER(aResult);if(mLength<=0||aIndex<0||aIndex>=mLength){returnNS_ERROR_FAILURE;}if(!mListRoot){returnNS_ERROR_FAILURE;}if(aIndex==0){*aResult=mListRoot;NS_ADDREF(*aResult);returnNS_OK;}int32_tcnt=0;nsCOMPtr<nsISHTransaction>tempPtr;rv=GetRootTransaction(getter_AddRefs(tempPtr));if(NS_FAILED(rv)||!tempPtr){returnNS_ERROR_FAILURE;}while(true){nsCOMPtr<nsISHTransaction>ptr;rv=tempPtr->GetNext(getter_AddRefs(ptr));if(NS_SUCCEEDED(rv)&&ptr){cnt++;if(cnt==aIndex){ptr.forget(aResult);break;}else{tempPtr=ptr;continue;}}else{returnNS_ERROR_FAILURE;}}returnNS_OK;}/* Get the index of a given entry */NS_IMETHODIMPnsSHistory::GetIndexOfEntry(nsISHEntry*aSHEntry,int32_t*aResult){NS_ENSURE_ARG(aSHEntry);NS_ENSURE_ARG_POINTER(aResult);*aResult=-1;if(mLength<=0){returnNS_ERROR_FAILURE;}nsCOMPtr<nsISHTransaction>currentTxn;int32_tcnt=0;nsresultrv=GetRootTransaction(getter_AddRefs(currentTxn));if(NS_FAILED(rv)||!currentTxn){returnNS_ERROR_FAILURE;}while(true){nsCOMPtr<nsISHEntry>entry;rv=currentTxn->GetSHEntry(getter_AddRefs(entry));if(NS_FAILED(rv)||!entry){returnNS_ERROR_FAILURE;}if(aSHEntry==entry){*aResult=cnt;break;}rv=currentTxn->GetNext(getter_AddRefs(currentTxn));if(NS_FAILED(rv)||!currentTxn){returnNS_ERROR_FAILURE;}cnt++;}returnNS_OK;}#ifdef DEBUGnsresultnsSHistory::PrintHistory(){nsCOMPtr<nsISHTransaction>txn;int32_tindex=0;nsresultrv;if(!mListRoot){returnNS_ERROR_FAILURE;}txn=mListRoot;while(1){if(!txn){break;}nsCOMPtr<nsISHEntry>entry;rv=txn->GetSHEntry(getter_AddRefs(entry));if(NS_FAILED(rv)&&!entry){returnNS_ERROR_FAILURE;}nsCOMPtr<nsILayoutHistoryState>layoutHistoryState;nsCOMPtr<nsIURI>uri;nsXPIDLStringtitle;entry->GetLayoutHistoryState(getter_AddRefs(layoutHistoryState));entry->GetURI(getter_AddRefs(uri));entry->GetTitle(getter_Copies(title));#if 0 nsAutoCString url; if (uri) { uri->GetSpec(url); } printf("**** SH Transaction #%d, Entry = %x\n", index, entry.get()); printf("\t\t URL = %s\n", url.get()); printf("\t\t Title = %s\n", NS_LossyConvertUTF16toASCII(title).get()); printf("\t\t layout History Data = %x\n", layoutHistoryState.get());#endifnsCOMPtr<nsISHTransaction>next;rv=txn->GetNext(getter_AddRefs(next));if(NS_SUCCEEDED(rv)&&next){txn=next;index++;continue;}else{break;}}returnNS_OK;}#endifNS_IMETHODIMPnsSHistory::GetRootTransaction(nsISHTransaction**aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=mListRoot;NS_IF_ADDREF(*aResult);returnNS_OK;}/* Get the max size of the history list */NS_IMETHODIMPnsSHistory::GetMaxLength(int32_t*aResult){NS_ENSURE_ARG_POINTER(aResult);*aResult=gHistoryMaxSize;returnNS_OK;}/* Set the max size of the history list */NS_IMETHODIMPnsSHistory::SetMaxLength(int32_taMaxSize){if(aMaxSize<0){returnNS_ERROR_ILLEGAL_VALUE;}gHistoryMaxSize=aMaxSize;if(mLength>aMaxSize){PurgeHistory(mLength-aMaxSize);}returnNS_OK;}NS_IMETHODIMPnsSHistory::PurgeHistory(int32_taEntries){if(mLength<=0||aEntries<=0){returnNS_ERROR_FAILURE;}aEntries=std::min(aEntries,mLength);boolpurgeHistory=true;NOTIFY_LISTENERS_CANCELABLE(OnHistoryPurge,purgeHistory,(aEntries,&purgeHistory));if(!purgeHistory){// Listener asked us not to purgereturnNS_SUCCESS_LOSS_OF_INSIGNIFICANT_DATA;}int32_tcnt=0;while(cnt<aEntries){nsCOMPtr<nsISHTransaction>nextTxn;if(mListRoot){mListRoot->GetNext(getter_AddRefs(nextTxn));mListRoot->SetNext(nullptr);}mListRoot=nextTxn;if(mListRoot){mListRoot->SetPrev(nullptr);}cnt++;}mLength-=cnt;mIndex-=cnt;// All following partial histories will be deleted in this case.mEntriesInFollowingPartialHistories=0;// Now if we were not at the end of the history, mIndex could have// become far too negative. If so, just set it to -1.if(mIndex<-1){mIndex=-1;}NOTIFY_LISTENERS(OnLengthChanged,(mLength));NOTIFY_LISTENERS(OnIndexChanged,(mIndex))if(mRootDocShell){mRootDocShell->HistoryPurged(cnt);}returnNS_OK;}NS_IMETHODIMPnsSHistory::AddSHistoryListener(nsISHistoryListener*aListener){NS_ENSURE_ARG_POINTER(aListener);// Check if the listener supports Weak Reference. This is a must.// This listener functionality is used by embedders and we want to// have the right ownership with who ever listens to SHistorynsWeakPtrlistener=do_GetWeakReference(aListener);if(!listener){returnNS_ERROR_FAILURE;}returnmListeners.AppendElementUnlessExists(listener)?NS_OK:NS_ERROR_OUT_OF_MEMORY;}NS_IMETHODIMPnsSHistory::RemoveSHistoryListener(nsISHistoryListener*aListener){// Make sure the listener that wants to be removed is the// one we have in store.nsWeakPtrlistener=do_GetWeakReference(aListener);mListeners.RemoveElement(listener);returnNS_OK;}NS_IMETHODIMPnsSHistory::SetPartialSHistoryListener(nsIPartialSHistoryListener*aListener){mPartialHistoryListener=do_GetWeakReference(aListener);returnNS_OK;}/* Replace an entry in the History list at a particular index. * Do not update index or count. */NS_IMETHODIMPnsSHistory::ReplaceEntry(int32_taIndex,nsISHEntry*aReplaceEntry){NS_ENSURE_ARG(aReplaceEntry);nsresultrv;nsCOMPtr<nsISHTransaction>currentTxn;if(!mListRoot){// Session History is not initialised.returnNS_ERROR_FAILURE;}rv=GetTransactionAtIndex(aIndex,getter_AddRefs(currentTxn));if(currentTxn){nsCOMPtr<nsISHistory>shistoryOfEntry;aReplaceEntry->GetSHistory(getter_AddRefs(shistoryOfEntry));if(shistoryOfEntry&&shistoryOfEntry!=this){NS_WARNING("The entry has been associated to another nsISHistory instance. ""Try nsISHEntry.clone() and nsISHEntry.abandonBFCacheEntry() ""first if you're copying an entry from another nsISHistory.");returnNS_ERROR_FAILURE;}aReplaceEntry->SetSHistory(this);NOTIFY_LISTENERS(OnHistoryReplaceEntry,(aIndex));// Set the replacement entry in the transactionrv=currentTxn->SetSHEntry(aReplaceEntry);rv=currentTxn->SetPersist(true);}returnrv;}NS_IMETHODIMPnsSHistory::NotifyOnHistoryReload(nsIURI*aReloadURI,uint32_taReloadFlags,bool*aCanReload){NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload,*aCanReload,(aReloadURI,aReloadFlags,aCanReload));returnNS_OK;}NS_IMETHODIMPnsSHistory::EvictOutOfRangeContentViewers(int32_taIndex){// Check our per SHistory object limit in the currently navigated SHistoryEvictOutOfRangeWindowContentViewers(aIndex);// Check our total limit across all SHistory objectsGloballyEvictContentViewers();returnNS_OK;}NS_IMETHODIMPnsSHistory::EvictAllContentViewers(){// XXXbz we don't actually do a good job of evicting things as we should, so// we might have viewers quite far from mIndex. So just evict everything.nsCOMPtr<nsISHTransaction>trans=mListRoot;while(trans){EvictContentViewerForTransaction(trans);nsCOMPtr<nsISHTransaction>temp=trans;temp->GetNext(getter_AddRefs(trans));}returnNS_OK;}NS_IMETHODIMPnsSHistory::GetCanGoBack(bool*aCanGoBack){NS_ENSURE_ARG_POINTER(aCanGoBack);if(mGlobalIndexOffset){*aCanGoBack=true;returnNS_OK;}int32_tindex=-1;NS_ENSURE_SUCCESS(GetIndex(&index),NS_ERROR_FAILURE);if(index>0){*aCanGoBack=true;returnNS_OK;}*aCanGoBack=false;returnNS_OK;}NS_IMETHODIMPnsSHistory::GetCanGoForward(bool*aCanGoForward){NS_ENSURE_ARG_POINTER(aCanGoForward);if(mEntriesInFollowingPartialHistories){*aCanGoForward=true;returnNS_OK;}int32_tindex=-1;int32_tcount=-1;NS_ENSURE_SUCCESS(GetIndex(&index),NS_ERROR_FAILURE);NS_ENSURE_SUCCESS(GetCount(&count),NS_ERROR_FAILURE);if(index>=0&&index<(count-1)){*aCanGoForward=true;returnNS_OK;}*aCanGoForward=false;returnNS_OK;}NS_IMETHODIMPnsSHistory::GoBack(){boolcanGoBack=false;GetCanGoBack(&canGoBack);if(!canGoBack){returnNS_ERROR_UNEXPECTED;}returnLoadEntry(mIndex-1,nsIDocShellLoadInfo::loadHistory,HIST_CMD_BACK);}NS_IMETHODIMPnsSHistory::GoForward(){boolcanGoForward=false;GetCanGoForward(&canGoForward);if(!canGoForward){returnNS_ERROR_UNEXPECTED;}returnLoadEntry(mIndex+1,nsIDocShellLoadInfo::loadHistory,HIST_CMD_FORWARD);}NS_IMETHODIMPnsSHistory::Reload(uint32_taReloadFlags){nsDocShellInfoLoadTypeloadType;if(aReloadFlags&nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY&&aReloadFlags&nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE){loadType=nsIDocShellLoadInfo::loadReloadBypassProxyAndCache;}elseif(aReloadFlags&nsIWebNavigation::LOAD_FLAGS_BYPASS_PROXY){loadType=nsIDocShellLoadInfo::loadReloadBypassProxy;}elseif(aReloadFlags&nsIWebNavigation::LOAD_FLAGS_BYPASS_CACHE){loadType=nsIDocShellLoadInfo::loadReloadBypassCache;}elseif(aReloadFlags&nsIWebNavigation::LOAD_FLAGS_CHARSET_CHANGE){loadType=nsIDocShellLoadInfo::loadReloadCharsetChange;}elseif(aReloadFlags&nsIWebNavigation::LOAD_FLAGS_ALLOW_MIXED_CONTENT){loadType=nsIDocShellLoadInfo::loadReloadMixedContent;}else{loadType=nsIDocShellLoadInfo::loadReloadNormal;}// We are reloading. Send Reload notifications.// nsDocShellLoadFlagType is not public, where as nsIWebNavigation// is public. So send the reload notifications with the// nsIWebNavigation flags.boolcanNavigate=true;nsCOMPtr<nsIURI>currentURI;GetCurrentURI(getter_AddRefs(currentURI));NOTIFY_LISTENERS_CANCELABLE(OnHistoryReload,canNavigate,(currentURI,aReloadFlags,&canNavigate));if(!canNavigate){returnNS_OK;}returnLoadEntry(mIndex,loadType,HIST_CMD_RELOAD);}NS_IMETHODIMPnsSHistory::ReloadCurrentEntry(){// Notify listenersboolcanNavigate=true;nsCOMPtr<nsIURI>currentURI;GetCurrentURI(getter_AddRefs(currentURI));NOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex,canNavigate,(mIndex,currentURI,&canNavigate));if(!canNavigate){returnNS_OK;}returnLoadEntry(mIndex,nsIDocShellLoadInfo::loadHistory,HIST_CMD_RELOAD);}NS_IMETHODIMPnsSHistory::RestoreToEntryAtIndex(int32_taIndex){mRequestedIndex=aIndex;nsCOMPtr<nsISHEntry>nextEntry;GetEntryAtIndex(mRequestedIndex,false,getter_AddRefs(nextEntry));if(!nextEntry){mRequestedIndex=-1;returnNS_ERROR_FAILURE;}// XXX We may want to ensure docshell is currently holding about:blankreturnInitiateLoad(nextEntry,mRootDocShell,nsIDocShellLoadInfo::loadHistory);}voidnsSHistory::EvictOutOfRangeWindowContentViewers(int32_taIndex){// XXX rename method to EvictContentViewersExceptAroundIndex, or something.// We need to release all content viewers that are no longer in the range//// aIndex - VIEWER_WINDOW to aIndex + VIEWER_WINDOW//// to ensure that this SHistory object isn't responsible for more than// VIEWER_WINDOW content viewers. But our job is complicated by the// fact that two transactions which are related by either hash navigations or// history.pushState will have the same content viewer.//// To illustrate the issue, suppose VIEWER_WINDOW = 3 and we have four// linked transactions in our history. Suppose we then add a new content// viewer and call into this function. So the history looks like://// A A A A B// + *//// where the letters are content viewers and + and * denote the beginning and// end of the range aIndex +/- VIEWER_WINDOW.//// Although one copy of the content viewer A exists outside the range, we// don't want to evict A, because it has other copies in range!//// We therefore adjust our eviction strategy to read://// Evict each content viewer outside the range aIndex -/+// VIEWER_WINDOW, unless that content viewer also appears within the// range.//// (Note that it's entirely legal to have two copies of one content viewer// separated by a different content viewer -- call pushState twice, go back// once, and refresh -- so we can't rely on identical viewers only appearing// adjacent to one another.)if(aIndex<0){return;}NS_ENSURE_TRUE_VOID(aIndex<mLength);// Calculate the range that's safe from eviction.int32_tstartSafeIndex=std::max(0,aIndex-nsISHistory::VIEWER_WINDOW);int32_tendSafeIndex=std::min(mLength,aIndex+nsISHistory::VIEWER_WINDOW);LOG(("EvictOutOfRangeWindowContentViewers(index=%d), ""mLength=%d. Safe range [%d, %d]",aIndex,mLength,startSafeIndex,endSafeIndex));// The content viewers in range aIndex -/+ VIEWER_WINDOW will not be// evicted. Collect a set of them so we don't accidentally evict one of them// if it appears outside this range.nsCOMArray<nsIContentViewer>safeViewers;nsCOMPtr<nsISHTransaction>trans;GetTransactionAtIndex(startSafeIndex,getter_AddRefs(trans));for(int32_ti=startSafeIndex;trans&&i<=endSafeIndex;i++){nsCOMPtr<nsIContentViewer>viewer=GetContentViewerForTransaction(trans);safeViewers.AppendObject(viewer);nsCOMPtr<nsISHTransaction>temp=trans;temp->GetNext(getter_AddRefs(trans));}// Walk the SHistory list and evict any content viewers that aren't safe.GetTransactionAtIndex(0,getter_AddRefs(trans));while(trans){nsCOMPtr<nsIContentViewer>viewer=GetContentViewerForTransaction(trans);if(safeViewers.IndexOf(viewer)==-1){EvictContentViewerForTransaction(trans);}nsCOMPtr<nsISHTransaction>temp=trans;temp->GetNext(getter_AddRefs(trans));}}namespace{classTransactionAndDistance{public:TransactionAndDistance(nsSHistory*aSHistory,nsISHTransaction*aTrans,uint32_taDist):mSHistory(aSHistory),mTransaction(aTrans),mLastTouched(0),mDistance(aDist){mViewer=GetContentViewerForTransaction(aTrans);NS_ASSERTION(mViewer,"Transaction should have a content viewer");nsCOMPtr<nsISHEntry>shentry;mTransaction->GetSHEntry(getter_AddRefs(shentry));nsCOMPtr<nsISHEntryInternal>shentryInternal=do_QueryInterface(shentry);if(shentryInternal){shentryInternal->GetLastTouched(&mLastTouched);}else{NS_WARNING("Can't cast to nsISHEntryInternal?");}}booloperator<(constTransactionAndDistance&aOther)const{// Compare distances first, and fall back to last-accessed times.if(aOther.mDistance!=this->mDistance){returnthis->mDistance<aOther.mDistance;}returnthis->mLastTouched<aOther.mLastTouched;}booloperator==(constTransactionAndDistance&aOther)const{// This is a little silly; we need == so the default comaprator can be// instantiated, but this function is never actually called when we sort// the list of TransactionAndDistance objects.returnaOther.mDistance==this->mDistance&&aOther.mLastTouched==this->mLastTouched;}RefPtr<nsSHistory>mSHistory;nsCOMPtr<nsISHTransaction>mTransaction;nsCOMPtr<nsIContentViewer>mViewer;uint32_tmLastTouched;int32_tmDistance;};}// namespace// staticvoidnsSHistory::GloballyEvictContentViewers(){// First, collect from each SHistory object the transactions which have a// cached content viewer. Associate with each transaction its distance from// its SHistory's current index.nsTArray<TransactionAndDistance>transactions;for(autoshist:gSHistoryList){// Maintain a list of the transactions which have viewers and belong to// this particular shist object. We'll add this list to the global list,// |transactions|, eventually.nsTArray<TransactionAndDistance>shTransactions;// Content viewers are likely to exist only within shist->mIndex -/+// VIEWER_WINDOW, so only search within that range.//// A content viewer might exist outside that range due to either://// * history.pushState or hash navigations, in which case a copy of the// content viewer should exist within the range, or//// * bugs which cause us not to call nsSHistory::EvictContentViewers()// often enough. Once we do call EvictContentViewers() for the// SHistory object in question, we'll do a full search of its history// and evict the out-of-range content viewers, so we don't bother here.//int32_tstartIndex=std::max(0,shist->mIndex-nsISHistory::VIEWER_WINDOW);int32_tendIndex=std::min(shist->mLength-1,shist->mIndex+nsISHistory::VIEWER_WINDOW);nsCOMPtr<nsISHTransaction>trans;shist->GetTransactionAtIndex(startIndex,getter_AddRefs(trans));for(int32_ti=startIndex;trans&&i<=endIndex;i++){nsCOMPtr<nsIContentViewer>contentViewer=GetContentViewerForTransaction(trans);if(contentViewer){// Because one content viewer might belong to multiple SHEntries, we// have to search through shTransactions to see if we already know// about this content viewer. If we find the viewer, update its// distance from the SHistory's index and continue.boolfound=false;for(uint32_tj=0;j<shTransactions.Length();j++){TransactionAndDistance&container=shTransactions[j];if(container.mViewer==contentViewer){container.mDistance=std::min(container.mDistance,DeprecatedAbs(i-shist->mIndex));found=true;break;}}// If we didn't find a TransactionAndDistance for this content viewer,// make a new one.if(!found){TransactionAndDistancecontainer(shist,trans,DeprecatedAbs(i-shist->mIndex));shTransactions.AppendElement(container);}}nsCOMPtr<nsISHTransaction>temp=trans;temp->GetNext(getter_AddRefs(trans));}// We've found all the transactions belonging to shist which have viewers.// Add those transactions to our global list and move on.transactions.AppendElements(shTransactions);}// We now have collected all cached content viewers. First check that we// have enough that we actually need to evict some.if((int32_t)transactions.Length()<=sHistoryMaxTotalViewers){return;}// If we need to evict, sort our list of transactions and evict the largest// ones. (We could of course get better algorithmic complexity here by using// a heap or something more clever. But sHistoryMaxTotalViewers isn't large,// so let's not worry about it.)transactions.Sort();for(int32_ti=transactions.Length()-1;i>=sHistoryMaxTotalViewers;--i){(transactions[i].mSHistory)->EvictContentViewerForTransaction(transactions[i].mTransaction);}}nsresultnsSHistory::EvictExpiredContentViewerForEntry(nsIBFCacheEntry*aEntry){int32_tstartIndex=std::max(0,mIndex-nsISHistory::VIEWER_WINDOW);int32_tendIndex=std::min(mLength-1,mIndex+nsISHistory::VIEWER_WINDOW);nsCOMPtr<nsISHTransaction>trans;GetTransactionAtIndex(startIndex,getter_AddRefs(trans));int32_ti;for(i=startIndex;trans&&i<=endIndex;++i){nsCOMPtr<nsISHEntry>entry;trans->GetSHEntry(getter_AddRefs(entry));// Does entry have the same BFCacheEntry as the argument to this method?if(entry->HasBFCacheEntry(aEntry)){break;}nsCOMPtr<nsISHTransaction>temp=trans;temp->GetNext(getter_AddRefs(trans));}if(i>endIndex){returnNS_OK;}if(i==mIndex){NS_WARNING("How did the current SHEntry expire?");returnNS_OK;}EvictContentViewerForTransaction(trans);returnNS_OK;}NS_IMETHODIMPnsSHistory::AddToExpirationTracker(nsIBFCacheEntry*aEntry){RefPtr<nsSHEntryShared>entry=static_cast<nsSHEntryShared*>(aEntry);if(!mHistoryTracker||!entry){returnNS_ERROR_FAILURE;}mHistoryTracker->AddObject(entry);returnNS_OK;}NS_IMETHODIMPnsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry*aEntry){RefPtr<nsSHEntryShared>entry=static_cast<nsSHEntryShared*>(aEntry);MOZ_ASSERT(mHistoryTracker&&!mHistoryTracker->IsEmpty());if(!mHistoryTracker||!entry){returnNS_ERROR_FAILURE;}mHistoryTracker->RemoveObject(entry);returnNS_OK;}// Evicts all content viewers in all history objects. This is very// inefficient, because it requires a linear search through all SHistory// objects for each viewer to be evicted. However, this method is called// infrequently -- only when the disk or memory cache is cleared.// staticvoidnsSHistory::GloballyEvictAllContentViewers(){int32_tmaxViewers=sHistoryMaxTotalViewers;sHistoryMaxTotalViewers=0;GloballyEvictContentViewers();sHistoryMaxTotalViewers=maxViewers;}voidGetDynamicChildren(nsISHContainer*aContainer,nsTArray<nsID>&aDocshellIDs,boolaOnlyTopLevelDynamic){int32_tcount=0;aContainer->GetChildCount(&count);for(int32_ti=0;i<count;++i){nsCOMPtr<nsISHEntry>child;aContainer->GetChildAt(i,getter_AddRefs(child));if(child){booldynAdded=false;child->IsDynamicallyAdded(&dynAdded);if(dynAdded){nsIDdocshellID=child->DocshellID();aDocshellIDs.AppendElement(docshellID);}if(!dynAdded||!aOnlyTopLevelDynamic){nsCOMPtr<nsISHContainer>childAsContainer=do_QueryInterface(child);if(childAsContainer){GetDynamicChildren(childAsContainer,aDocshellIDs,aOnlyTopLevelDynamic);}}}}}boolRemoveFromSessionHistoryContainer(nsISHContainer*aContainer,nsTArray<nsID>&aDocshellIDs){nsCOMPtr<nsISHEntry>root=do_QueryInterface(aContainer);NS_ENSURE_TRUE(root,false);booldidRemove=false;int32_tchildCount=0;aContainer->GetChildCount(&childCount);for(int32_ti=childCount-1;i>=0;--i){nsCOMPtr<nsISHEntry>child;aContainer->GetChildAt(i,getter_AddRefs(child));if(child){nsIDdocshelldID=child->DocshellID();if(aDocshellIDs.Contains(docshelldID)){didRemove=true;aContainer->RemoveChild(child);}else{nsCOMPtr<nsISHContainer>container=do_QueryInterface(child);if(container){boolchildRemoved=RemoveFromSessionHistoryContainer(container,aDocshellIDs);if(childRemoved){didRemove=true;}}}}}returndidRemove;}boolRemoveChildEntries(nsISHistory*aHistory,int32_taIndex,nsTArray<nsID>&aEntryIDs){nsCOMPtr<nsISHEntry>rootHE;aHistory->GetEntryAtIndex(aIndex,false,getter_AddRefs(rootHE));nsCOMPtr<nsISHContainer>root=do_QueryInterface(rootHE);returnroot?RemoveFromSessionHistoryContainer(root,aEntryIDs):false;}boolIsSameTree(nsISHEntry*aEntry1,nsISHEntry*aEntry2){if(!aEntry1&&!aEntry2){returntrue;}if((!aEntry1&&aEntry2)||(aEntry1&&!aEntry2)){returnfalse;}uint32_tid1,id2;aEntry1->GetID(&id1);aEntry2->GetID(&id2);if(id1!=id2){returnfalse;}nsCOMPtr<nsISHContainer>container1=do_QueryInterface(aEntry1);nsCOMPtr<nsISHContainer>container2=do_QueryInterface(aEntry2);int32_tcount1,count2;container1->GetChildCount(&count1);container2->GetChildCount(&count2);// We allow null entries in the end of the child list.int32_tcount=std::max(count1,count2);for(int32_ti=0;i<count;++i){nsCOMPtr<nsISHEntry>child1,child2;container1->GetChildAt(i,getter_AddRefs(child1));container2->GetChildAt(i,getter_AddRefs(child2));if(!IsSameTree(child1,child2)){returnfalse;}}returntrue;}boolnsSHistory::RemoveDuplicate(int32_taIndex,boolaKeepNext){NS_ASSERTION(aIndex>=0,"aIndex must be >= 0!");NS_ASSERTION(aIndex!=0||aKeepNext,"If we're removing index 0 we must be keeping the next");NS_ASSERTION(aIndex!=mIndex,"Shouldn't remove mIndex!");int32_tcompareIndex=aKeepNext?aIndex+1:aIndex-1;nsCOMPtr<nsISHEntry>root1,root2;GetEntryAtIndex(aIndex,false,getter_AddRefs(root1));GetEntryAtIndex(compareIndex,false,getter_AddRefs(root2));if(IsSameTree(root1,root2)){nsCOMPtr<nsISHTransaction>txToRemove,txToKeep,txNext,txPrev;GetTransactionAtIndex(aIndex,getter_AddRefs(txToRemove));GetTransactionAtIndex(compareIndex,getter_AddRefs(txToKeep));if(!txToRemove){returnfalse;}NS_ENSURE_TRUE(txToKeep,false);txToRemove->GetNext(getter_AddRefs(txNext));txToRemove->GetPrev(getter_AddRefs(txPrev));txToRemove->SetNext(nullptr);txToRemove->SetPrev(nullptr);if(aKeepNext){if(txPrev){txPrev->SetNext(txToKeep);}else{txToKeep->SetPrev(nullptr);}}else{txToKeep->SetNext(txNext);}if(aIndex==0&&aKeepNext){NS_ASSERTION(txToRemove==mListRoot,"Transaction at index 0 should be mListRoot!");// We're removing the very first session history transaction!mListRoot=txToKeep;}if(mRootDocShell){static_cast<nsDocShell*>(mRootDocShell)->HistoryTransactionRemoved(aIndex);}// Adjust our indices to reflect the removed transactionif(mIndex>aIndex){mIndex=mIndex-1;NOTIFY_LISTENERS(OnIndexChanged,(mIndex));}// NB: If the transaction we are removing is the transaction currently// being navigated to (mRequestedIndex) then we adjust the index// only if we're not keeping the next entry (because if we are keeping// the next entry (because the current is a duplicate of the next), then// that entry slides into the spot that we're currently pointing to.// We don't do this adjustment for mIndex because mIndex cannot equal// aIndex.// NB: We don't need to guard on mRequestedIndex being nonzero here,// because either they're strictly greater than aIndex which is at least// zero, or they are equal to aIndex in which case aKeepNext must be true// if aIndex is zero.if(mRequestedIndex>aIndex||(mRequestedIndex==aIndex&&!aKeepNext)){mRequestedIndex=mRequestedIndex-1;}--mLength;mEntriesInFollowingPartialHistories=0;NOTIFY_LISTENERS(OnLengthChanged,(mLength));returntrue;}returnfalse;}NS_IMETHODIMP_(void)nsSHistory::RemoveEntries(nsTArray<nsID>&aIDs,int32_taStartIndex){int32_tindex=aStartIndex;while(index>=0&&RemoveChildEntries(this,--index,aIDs)){}int32_tminIndex=index;index=aStartIndex;while(index>=0&&RemoveChildEntries(this,index++,aIDs)){}// We need to remove duplicate nsSHEntry trees.booldidRemove=false;while(index>minIndex){if(index!=mIndex){didRemove=RemoveDuplicate(index,index<mIndex)||didRemove;}--index;}if(didRemove&&mRootDocShell){mRootDocShell->DispatchLocationChangeEvent();}}voidnsSHistory::RemoveDynEntries(int32_taIndex,nsISHContainer*aContainer){// Remove dynamic entries which are at the index and belongs to the container.nsCOMPtr<nsISHContainer>container(aContainer);if(!container){nsCOMPtr<nsISHEntry>entry;GetEntryAtIndex(aIndex,false,getter_AddRefs(entry));container=do_QueryInterface(entry);}if(container){AutoTArray<nsID,16>toBeRemovedEntries;GetDynamicChildren(container,toBeRemovedEntries,true);if(toBeRemovedEntries.Length()){RemoveEntries(toBeRemovedEntries,aIndex);}}}NS_IMETHODIMPnsSHistory::UpdateIndex(){// Update the actual index with the right value.if(mIndex!=mRequestedIndex&&mRequestedIndex!=-1){mIndex=mRequestedIndex;NOTIFY_LISTENERS(OnIndexChanged,(mIndex))}mRequestedIndex=-1;returnNS_OK;}NS_IMETHODIMPnsSHistory::Stop(uint32_taStopFlags){// Not implementedreturnNS_OK;}NS_IMETHODIMPnsSHistory::GetDocument(nsIDOMDocument**aDocument){// Not implementedreturnNS_OK;}NS_IMETHODIMPnsSHistory::GetCurrentURI(nsIURI**aResultURI){NS_ENSURE_ARG_POINTER(aResultURI);nsresultrv;nsCOMPtr<nsISHEntry>currentEntry;rv=GetEntryAtIndex(mIndex,false,getter_AddRefs(currentEntry));if(NS_FAILED(rv)&&!currentEntry){returnrv;}rv=currentEntry->GetURI(aResultURI);returnrv;}NS_IMETHODIMPnsSHistory::GetReferringURI(nsIURI**aURI){*aURI=nullptr;// Not implementedreturnNS_OK;}NS_IMETHODIMPnsSHistory::SetSessionHistory(nsISHistory*aSessionHistory){// Not implementedreturnNS_OK;}NS_IMETHODIMPnsSHistory::GetSessionHistory(nsISHistory**aSessionHistory){// Not implementedreturnNS_OK;}NS_IMETHODIMPnsSHistory::LoadURIWithOptions(constchar16_t*aURI,uint32_taLoadFlags,nsIURI*aReferringURI,uint32_taReferrerPolicy,nsIInputStream*aPostStream,nsIInputStream*aExtraHeaderStream,nsIURI*aBaseURI,nsIPrincipal*aTriggeringPrincipal){returnNS_OK;}NS_IMETHODIMPnsSHistory::SetOriginAttributesBeforeLoading(JS::HandleValueaOriginAttributes){returnNS_OK;}NS_IMETHODIMPnsSHistory::LoadURI(constchar16_t*aURI,uint32_taLoadFlags,nsIURI*aReferringURI,nsIInputStream*aPostStream,nsIInputStream*aExtraHeaderStream,nsIPrincipal*aTriggeringPrincipal){returnNS_OK;}NS_IMETHODIMPnsSHistory::GotoIndex(int32_taGlobalIndex){// We provide abstraction of grouped session history for nsIWebNavigation// functions, so the index passed in here is global index.returnLoadEntry(aGlobalIndex-mGlobalIndexOffset,nsIDocShellLoadInfo::loadHistory,HIST_CMD_GOTOINDEX);}nsresultnsSHistory::LoadNextPossibleEntry(int32_taNewIndex,longaLoadType,uint32_taHistCmd){mRequestedIndex=-1;if(aNewIndex<mIndex){returnLoadEntry(aNewIndex-1,aLoadType,aHistCmd);}if(aNewIndex>mIndex){returnLoadEntry(aNewIndex+1,aLoadType,aHistCmd);}returnNS_ERROR_FAILURE;}NS_IMETHODIMPnsSHistory::LoadEntry(int32_taIndex,longaLoadType,uint32_taHistCmd){if(!mRootDocShell){returnNS_ERROR_FAILURE;}nsCOMPtr<nsIURI>nextURI;nsCOMPtr<nsISHEntry>prevEntry;nsCOMPtr<nsISHEntry>nextEntry;boolisCrossBrowserNavigation=false;if(aIndex<0||aIndex>=mLength){if(aIndex+mGlobalIndexOffset<0){// The global index is negative.returnNS_ERROR_FAILURE;}if(aIndex-mLength>=mEntriesInFollowingPartialHistories){// The global index exceeds max possible value.returnNS_ERROR_FAILURE;}// The global index is valid. Mark that we're going to navigate to another// partial history, but wait until we've notified all listeners before// actually do so.isCrossBrowserNavigation=true;}else{// This is a normal local history navigation.// Keep note of requested history index in mRequestedIndex.mRequestedIndex=aIndex;GetEntryAtIndex(mIndex,false,getter_AddRefs(prevEntry));GetEntryAtIndex(mRequestedIndex,false,getter_AddRefs(nextEntry));if(!nextEntry||!prevEntry){mRequestedIndex=-1;returnNS_ERROR_FAILURE;}// Remember that this entry is getting loaded at this point in the sequencensCOMPtr<nsISHEntryInternal>entryInternal=do_QueryInterface(nextEntry);if(entryInternal){entryInternal->SetLastTouched(++gTouchCounter);}// Get the uri for the entry we are about to visitnextEntry->GetURI(getter_AddRefs(nextURI));}MOZ_ASSERT(isCrossBrowserNavigation||(prevEntry&&nextEntry&&nextURI),"prevEntry, nextEntry and nextURI can be null only if isCrossBrowserNavigation is set");// Send appropriate listener notifications. Note nextURI could be null in case// of grouped session history navigation.boolcanNavigate=true;if(aHistCmd==HIST_CMD_BACK){// We are going back one entry. Send GoBack notificationsNOTIFY_LISTENERS_CANCELABLE(OnHistoryGoBack,canNavigate,(nextURI,&canNavigate));}elseif(aHistCmd==HIST_CMD_FORWARD){// We are going forward. Send GoForward notificationNOTIFY_LISTENERS_CANCELABLE(OnHistoryGoForward,canNavigate,(nextURI,&canNavigate));}elseif(aHistCmd==HIST_CMD_GOTOINDEX){// We are going somewhere else. This is not reload eitherNOTIFY_LISTENERS_CANCELABLE(OnHistoryGotoIndex,canNavigate,(aIndex,nextURI,&canNavigate));}if(!canNavigate){// If the listener asked us not to proceed with// the operation, simply return.mRequestedIndex=-1;returnNS_OK;// XXX Maybe I can return some other error code?}if(isCrossBrowserNavigation){nsCOMPtr<nsIPartialSHistoryListener>listener=do_QueryReferent(mPartialHistoryListener);if(!listener){returnNS_ERROR_FAILURE;}// CreateAboutBlankContentViewer would check for permit unload, fire proper// pagehide / unload events and transfer content viewer ownership to SHEntry.if(NS_FAILED(mRootDocShell->CreateAboutBlankContentViewer(nullptr))){returnNS_ERROR_FAILURE;}returnlistener->OnRequestCrossBrowserNavigation(aIndex+mGlobalIndexOffset);}if(mRequestedIndex==mIndex){// Possibly a reload casereturnInitiateLoad(nextEntry,mRootDocShell,aLoadType);}// Going back or forward.booldifferenceFound=false;nsresultrv=LoadDifferingEntries(prevEntry,nextEntry,mRootDocShell,aLoadType,differenceFound);if(!differenceFound){// We did not find any differences. Go further in the history.returnLoadNextPossibleEntry(aIndex,aLoadType,aHistCmd);}returnrv;}nsresultnsSHistory::LoadDifferingEntries(nsISHEntry*aPrevEntry,nsISHEntry*aNextEntry,nsIDocShell*aParent,longaLoadType,bool&aDifferenceFound){if(!aPrevEntry||!aNextEntry||!aParent){returnNS_ERROR_FAILURE;}nsresultresult=NS_OK;uint32_tprevID,nextID;aPrevEntry->GetID(&prevID);aNextEntry->GetID(&nextID);// Check the IDs to verify if the pages are different.if(prevID!=nextID){aDifferenceFound=true;// Set the Subframe flag if not navigating the root docshell.aNextEntry->SetIsSubFrame(aParent!=mRootDocShell);returnInitiateLoad(aNextEntry,aParent,aLoadType);}// The entries are the same, so compare any child framesint32_tpcnt=0;int32_tncnt=0;int32_tdsCount=0;nsCOMPtr<nsISHContainer>prevContainer(do_QueryInterface(aPrevEntry));nsCOMPtr<nsISHContainer>nextContainer(do_QueryInterface(aNextEntry));if(!prevContainer||!nextContainer){returnNS_ERROR_FAILURE;}prevContainer->GetChildCount(&pcnt);nextContainer->GetChildCount(&ncnt);aParent->GetChildCount(&dsCount);// Create an array for child docshells.nsCOMArray<nsIDocShell>docshells;for(int32_ti=0;i<dsCount;++i){nsCOMPtr<nsIDocShellTreeItem>treeItem;aParent->GetChildAt(i,getter_AddRefs(treeItem));nsCOMPtr<nsIDocShell>shell=do_QueryInterface(treeItem);if(shell){docshells.AppendElement(shell.forget());}}// Search for something to load next.for(int32_ti=0;i<ncnt;++i){// First get an entry which may cause a new page to be loaded.nsCOMPtr<nsISHEntry>nChild;nextContainer->GetChildAt(i,getter_AddRefs(nChild));if(!nChild){continue;}nsIDdocshellID=nChild->DocshellID();// Then find the associated docshell.nsIDocShell*dsChild=nullptr;int32_tcount=docshells.Count();for(int32_tj=0;j<count;++j){nsIDocShell*shell=docshells[j];nsIDshellID=shell->HistoryID();if(shellID==docshellID){dsChild=shell;break;}}if(!dsChild){continue;}// Then look at the previous entries to see if there was// an entry for the docshell.nsCOMPtr<nsISHEntry>pChild;for(int32_tk=0;k<pcnt;++k){nsCOMPtr<nsISHEntry>child;prevContainer->GetChildAt(k,getter_AddRefs(child));if(child){nsIDdID=child->DocshellID();if(dID==docshellID){pChild=child;break;}}}// Finally recursively call this method.// This will either load a new page to shell or some subshell or// do nothing.LoadDifferingEntries(pChild,nChild,dsChild,aLoadType,aDifferenceFound);}returnresult;}nsresultnsSHistory::InitiateLoad(nsISHEntry*aFrameEntry,nsIDocShell*aFrameDS,longaLoadType){NS_ENSURE_STATE(aFrameDS&&aFrameEntry);nsCOMPtr<nsIDocShellLoadInfo>loadInfo;/* Set the loadType in the SHEntry too to what was passed on. * This will be passed on to child subframes later in nsDocShell, * so that proper loadType is maintained through out a frameset */aFrameEntry->SetLoadType(aLoadType);aFrameDS->CreateLoadInfo(getter_AddRefs(loadInfo));loadInfo->SetLoadType(aLoadType);loadInfo->SetSHEntry(aFrameEntry);nsCOMPtr<nsIURI>originalURI;aFrameEntry->GetOriginalURI(getter_AddRefs(originalURI));loadInfo->SetOriginalURI(originalURI);boolloadReplace;aFrameEntry->GetLoadReplace(&loadReplace);loadInfo->SetLoadReplace(loadReplace);nsCOMPtr<nsIURI>nextURI;aFrameEntry->GetURI(getter_AddRefs(nextURI));// Time to initiate a document loadreturnaFrameDS->LoadURI(nextURI,loadInfo,nsIWebNavigation::LOAD_FLAGS_NONE,false);}NS_IMETHODIMPnsSHistory::SetRootDocShell(nsIDocShell*aDocShell){mRootDocShell=aDocShell;// Init mHistoryTracker on setting mRootDocShell so we can bind its event// target to the tabGroup.if(mRootDocShell){nsCOMPtr<nsPIDOMWindowOuter>win=mRootDocShell->GetWindow();if(!win){returnNS_ERROR_UNEXPECTED;}// Seamonkey moves shistory between <xul:browser>s when restoring a tab.// Let's try not to break our friend too badly...if(mHistoryTracker){NS_WARNING("Change the root docshell of a shistory is unsafe and ""potentially problematic.");mHistoryTracker->AgeAllGenerations();}RefPtr<mozilla::dom::TabGroup>tabGroup=win->TabGroup();mHistoryTracker=mozilla::MakeUnique<HistoryTracker>(this,mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),tabGroup->EventTargetFor(mozilla::TaskCategory::Other));}returnNS_OK;}NS_IMETHODIMPnsSHistory::GetSHistoryEnumerator(nsISimpleEnumerator**aEnumerator){NS_ENSURE_ARG_POINTER(aEnumerator);RefPtr<nsSHEnumerator>iterator=newnsSHEnumerator(this);iterator.forget(aEnumerator);returnNS_OK;}NS_IMETHODIMPnsSHistory::OnAttachGroupedSHistory(int32_taOffset){NS_ENSURE_TRUE(!mIsPartial&&mRootDocShell,NS_ERROR_UNEXPECTED);NS_ENSURE_TRUE(aOffset>=0,NS_ERROR_ILLEGAL_VALUE);mIsPartial=true;mGlobalIndexOffset=aOffset;// The last attached history is always at the end of the group.mEntriesInFollowingPartialHistories=0;// Setting grouped history info may change canGoBack / canGoForward.// Send a location change to update these values.mRootDocShell->DispatchLocationChangeEvent();returnNS_OK;}nsSHEnumerator::nsSHEnumerator(nsSHistory*aSHistory):mIndex(-1){mSHistory=aSHistory;}nsSHEnumerator::~nsSHEnumerator(){mSHistory=nullptr;}NS_IMPL_ISUPPORTS(nsSHEnumerator,nsISimpleEnumerator)NS_IMETHODIMPnsSHEnumerator::HasMoreElements(bool*aReturn){int32_tcnt;*aReturn=false;mSHistory->GetCount(&cnt);if(mIndex>=-1&&mIndex<(cnt-1)){*aReturn=true;}returnNS_OK;}NS_IMETHODIMPnsSHEnumerator::GetNext(nsISupports**aItem){NS_ENSURE_ARG_POINTER(aItem);int32_tcnt=0;nsresultresult=NS_ERROR_FAILURE;mSHistory->GetCount(&cnt);if(mIndex<(cnt-1)){mIndex++;nsCOMPtr<nsISHEntry>hEntry;result=mSHistory->GetEntryAtIndex(mIndex,false,getter_AddRefs(hEntry));if(hEntry){result=CallQueryInterface(hEntry,aItem);}}returnresult;}